Collaborators

Include the names of your collaborators here.

Overview

This homework is dedicated to working with logistic regression for binary classification. You will explore a binary classification application and fit non-Bayesian logistic regression models by maximizing the likelihood via the glm() function R. Then you will fit Bayesian logistic regression models with the Laplace Approximation by programming the log-posterior function. You will identify the best model and make predictions to study the trends in the predicted event probability. You will conclude the application by tuning various non-Bayesian models. First you will tune a non-Bayesian logistic regression model with the elastic net penalty to identify to help identify the most important features in the model. You will visualize the predicted event probability trends from the tuned elastic net model and compare the results with your Bayesian models. Then you tune several advanced methods like neural networks and random forests. This last portion of the assignment introduces the basic syntax for training and tuning those advanced methods with caret. You will focus on assessing their performance via cross-validation and visualizing their predictive trends in order to compare their behavior to the simpler generalized linear models you previously fit!

You will use the tidyverse, coefplot, broom, MASS, glmnet, nnet, randomForest, and caret packages in this assignment. The caret package will prompt you to install nnet and randomForest if you do not have them installed already.

IMPORTANT: The RMarkdown assumes you have downloaded the data sets (CSV files) to the same directory you saved the template Rmarkdown file. If you do not have the CSV files in the correct location, the data will not be loaded correctly.

IMPORTANT!!!

Certain code chunks are created for you. Each code chunk has eval=FALSE set in the chunk options. You MUST change it to be eval=TRUE in order for the code chunks to be evaluated when rendering the document.

You are free to add more code chunks if you would like.

Load packages

This assignment will use packages from the tidyverse suite.

library(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.2 ──
## ✔ ggplot2 3.4.0     ✔ purrr   1.0.1
## ✔ tibble  3.1.8     ✔ dplyr   1.1.0
## ✔ tidyr   1.3.0     ✔ stringr 1.5.0
## ✔ readr   2.1.2     ✔ forcats 0.5.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()

This assignment also uses the broom package. The broom package is part of tidyverse and so you have it installed already. The broom package will be used via the :: operator later in the assignment and so you do not need to load it directly.

The caret package will be loaded later in the assignment and the glmnet, nnet, and randomForest packages will be loaded via caret.

Problem 01

The primary data set you will work with in this assignment is loaded for you in the code chunk below.

df1 <- readr::read_csv('hw10_binary_01.csv', col_names = TRUE)
## Rows: 225 Columns: 4
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (1): x1
## dbl (3): x2, x3, y
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

The data consists of 3 inputs, x1, x2, and x3, and one binary outcome, y. The glimpse below shows that x1 is a character and thus is a categorical input. The x2 and x3 inputs are continuous. The binary outcome has been encoded as y=1 for the EVENT and y=0 for the NON-EVENT.

df1 %>% glimpse()
## Rows: 225
## Columns: 4
## $ x1 <chr> "C", "B", "C", "B", "A", "C", "A", "A", "C", "C", "B", "C", "A", "C…
## $ x2 <dbl> 1.682873632, -1.033648456, 0.110854156, 2.032934019, -0.225540507, …
## $ x3 <dbl> -0.353085685, -0.778102544, 0.757536960, 0.639465847, 0.017483448, …
## $ y  <dbl> 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0…

It is always best to explore data before training predictive models. This assignment does not require you to create all figures necessary to sufficiently explore the data. This assignment focuses on various ways of exploring the relationships between the binary outcome and the inputs. You will thus not consider input correlation plots in this assignment. Please note that the inputs have already been standardized for you to streamline the visualizations and modeling. Realistic applications like the final project may have inputs with vastly different scales and so you will need to execute the preprocessing as part of the model training.

The code chunk below reshapes the wide-format df1 data into long-format, lf1. The continuous inputs, x1 and x2, are “gathered” and “stacked” on top of each other. The long-format data supports using facets associated with the continuous inputs. You will use the long-format data in some of the visualizations below.

lf1 <- df1 %>% 
  tibble::rowid_to_column() %>% 
  pivot_longer(c(x2, x3))

The glimpse below shows the continuous input names are contained in the name column and their values are contained in the value column.

lf1 %>% glimpse()
## Rows: 450
## Columns: 5
## $ rowid <int> 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11…
## $ x1    <chr> "C", "C", "B", "B", "C", "C", "B", "B", "A", "A", "C", "C", "A",…
## $ y     <dbl> 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0…
## $ name  <chr> "x2", "x3", "x2", "x3", "x2", "x3", "x2", "x3", "x2", "x3", "x2"…
## $ value <dbl> 1.682873632, -0.353085685, -1.033648456, -0.778102544, 0.1108541…

1a)

Let’s start with exploring the inputs.

Visualize the distributions of the continuous inputs as histograms in ggplot2.

It is up to you as to whether you use the wide format or long-format data. If you use the wide-format data you should create two separate figures. If you use the long-format data you should use facets for each continuous input.

SOLUTION

Add your code chunks here.

lf1 %>% ggplot(aes(x = value)) +
        geom_histogram(bins = 30, color = "black", fill = "lightblue") +
        facet_wrap(~ name, scales = "free_x")+
        theme_minimal()

1b)

Visualize the counts of the categorical input x1 with a bar chart in ggplot2. You MUST use the wide-format data for this visualization.

SOLUTION

Add your code chunks here.

df1 %>% ggplot(aes(x = x1)) +
        geom_bar(color = "black", fill = "lightblue")

1c)

Let’s examine if there are differences in the continuous input distributions based on the categorical input. You will use boxplots to focus on the distribution summary statistics.

You must use the long-format data for this figure. Create a boxplot in ggplot2 where the x aesthetic is the categorical input and the y aesthetic is the value column. You must include facets associated with the name column.

SOLUTION

Add your code chunks here.

lf1 %>% ggplot(aes(x = x1, y = value, fill = x1)) +
        geom_boxplot() +
        facet_wrap(~ name, scales = "free_y")

1d)

Let’s now focus on the binary outcome.

Visualize the counts of the binary outcome y with a bar chart in ggplot2. You MUST use the wide-format data for this visualization.

Is the binary outcome balanced?

SOLUTION

Add your code chunks here.

df1 %>% ggplot(aes(x = factor(y))) +
        geom_bar(color = "black", fill = "lightblue") +
        theme_minimal()

What do you think?
According to the image, I think their binary outcomes are not balanced.

1e)

Let’s see if the categorical input impacts the binary outcome.

Create a bar chart for the categorical input x1 with ggplot2 like you did in 1b). However, you must also map the fill aesthetic to as.factor(y).

The data type conversion function as.factor() can be applied in-line. This will force a categorical fill palette to be used rather than a continuous palette.

SOLUTION

Add your code chunks here.

df1 %>% ggplot(aes(x = x1, fill = as.factor(y))) +
        geom_bar(position = "dodge") +
        theme_minimal()

1f)

Let’s now visualize the binary outcome with respect to the continuous inputs. You will use the geom_jitter() function instead of the geom_point() function to create the scatter plot. The geom_jitter() function adds small amounts of random noise to “jitter” or perturb the locations of the points. This will make it easier to see the individual observations of the binary outcome. You MUST use the long-format data for this question.

Pipe the long-format data to ggplot() and map the x aesthetic to the value variable and the y aesthetic to the y variable. Add a geom_jitter() layer where you specify the height and width arguments to be 0.02 and 0, respectively. Do NOT set height and width within the aes() function. Facet by the continuous inputs by including a facet_wrap() layer with the facets “a function of” the name column.

SOLUTION

Add your code chunks here.

lf1 %>%
  ggplot(aes(x = value, y = y, color = as.factor(y))) +
  geom_jitter(height = 0.02, width = 0) +
  facet_wrap(~ name, scales = "free_x")

1g)

We can include a logistic regression smoother to help visualize the changes in the event probability. You will use the geom_smooth() function to do so, but you will need to change the arguments compared to previous assignments that focused on regression.

Create the same plot as 1f) but include geom_smooth() as a layer between geom_jitter() and facet_wrap(). Specify the formula argument to y ~ x, the method argument to be glm, and the method.args argument to be list(family = 'binomial').

The formula argument are “local” variables associated with the aesthetics. Thus the formula y ~ x means the y aesthetic is linearly related to the x aesthetic. However, by specifying method = glm and method.args = list(family = 'binomial') you are instructing geom_smooth() to fit a logistic regression model. Thus, you are actually specifying that the linear predictor, the log-odds ratio is linearly related to the x aesthetic.

SOLUTION

Add your code chunks here.

lf1 %>%
  ggplot(aes(x = value, y = y)) +
  geom_point() + 
  geom_jitter(height = 0.02, width = 0, alpha = 0.7) +
  geom_smooth(formula = y ~ x, method = "glm",method.args = list(family = 'binomial')) +
  facet_wrap(~ name)

1h)

Let’s check if the categorical input influences the event probability trends with respect to the continuous inputs.

Create the same figure as in 1g), except this time map the color and fill aesthetics within the geom_smooth() layer to the x1 variable. You must also map the color aesthetic within the geom_jitter() layer to the x1 variable.

Based on your figure do the trends appear to depend on the categorical input?

SOLUTION

Add your code chunks here.

lf1 %>%
  ggplot(aes(x = value, y = y)) +
  geom_jitter(aes(color = x1), height = 0.02, width = 0, alpha = 0.7) +
  geom_smooth(aes(color = x1, fill = x1), formula = y ~ x, method = "glm", method.args = list(family = "binomial")) +
  facet_wrap(~ name, scales = "free_x") +
  theme_minimal()

What do you think?
I think the event probability trends may indeed depend on the categorical input.

1i)

The previous figures used the “basic” formula of y ~ x within geom_smooth(). However, we can try more complex relationships within geom_smooth(). For example, let’s consider quadratic relationships between the log-odds ratio (linear predictor) and the continuous inputs.

Create the same figure as 1h), except this time specify the formula argument to be y ~ x + I(x^2). Use the same set of aesthetics as 1h) including the color and fill aesthetics.

Does the quadratic relationship appear to be consistent with the data for either of the 2 inputs?

SOLUTION

Add your code chunks here.

lf1 %>%
  ggplot(aes(x = value, y = y)) +
  geom_jitter(aes(color = x1), height = 0.02, width = 0, alpha = 0.7) +
  geom_smooth(aes(color = x1, fill = x1), formula = y ~ x + I(x^2), method = "glm", method.args = list(family = "binomial")) +
  facet_wrap(~ name, scales = "free_x") +
  theme_minimal()

What do you think?
Yes, they are consistent.

Problem 02

Now that you have explored the data, it’s time to start modeling! You will fit multiple non-Bayesian logistic regression models using glm(). These models will range from simple to complex. You do not need to standardize the continuous inputs, they have already been standardized for you. You can focus on the fitting the models.

BE CAREFUL!! Make sure you set the family argument in glm() correctly!!! The family argument was discussed earlier in the semester.

2a)

You must fit the following models:

A: Categorical input only
B: Linear additive features using the continuous inputs only
C: Linear additive features using all inputs (categorical and continuous)
D: Interact the categorical input with the continuous inputs
E: Add the categorical input to linear main effects and interaction between the continuous inputs
F: Interact the categorical input with main effect and interaction features associated with the continuous inputs
G: Add the categorical input to the linear main continuous effects, interaction between continuous, and quadratic continuous features
H: Interact the categorical input to the linear main continuous effects, interaction between continuous, and quadratic continuous features

You must name your models modA through modH.

You do not need to fit all models in a single code chunk.

SOLUTION

Add your code chunks here.

modA <- glm(y ~ x1, data = df1, family = "binomial")

modB <- glm(y ~ x2 + x3, data = df1, family = "binomial")

modC <- glm(y ~ x1 + x2 + x3, data = df1, family = "binomial")

modD <- glm(y ~ x1 * (x2 + x3), data = df1, family = "binomial")

modE <- glm(y ~ x1 + x2 * x3, data = df1, family = "binomial")

modF <- glm(y ~ x1 * (x2 * x3), data = df1, family = "binomial")

modG <- glm(y ~ x1 + (x2 * x3 + I(x2^2) + I(x3^2)), data = df1, family = "binomial")

modH <- glm(y ~ x1 * (x2 * x3 + I(x2^2) + I(x3^2)), data = df1, family = "binomial")
modH
## 
## Call:  glm(formula = y ~ x1 * (x2 * x3 + I(x2^2) + I(x3^2)), family = "binomial", 
##     data = df1)
## 
## Coefficients:
## (Intercept)          x1B          x1C           x2           x3      I(x2^2)  
##     -2.1614      -1.4239      -0.5378       0.2377       0.1860      -0.1697  
##     I(x3^2)        x2:x3       x1B:x2       x1C:x2       x1B:x3       x1C:x3  
##      1.6711      -0.2581       0.7046       4.0907      -0.1625      -1.3616  
## x1B:I(x2^2)  x1C:I(x2^2)  x1B:I(x3^2)  x1C:I(x3^2)    x1B:x2:x3    x1C:x2:x3  
##     -0.8564      -0.6776       0.7122       0.3194      -0.2825       2.0044  
## 
## Degrees of Freedom: 224 Total (i.e. Null);  207 Residual
## Null Deviance:       280.6 
## Residual Deviance: 142.2     AIC: 178.2

2b)

Which of the 8 models is the best? Which of the 8 models is the second best?

State the performance metric you used to make your selection.

HINT: The broom::glance() function will help here…

SOLUTION

Add your code chunks here.

library(broom)
glance(modA)$AIC
## [1] 279.4618
glance(modB)$AIC
## [1] 270.3974
glance(modC)$AIC
## [1] 265.1775
glance(modD)$AIC
## [1] 254.1712
glance(modE)$AIC
## [1] 267.1745
glance(modF)$AIC
## [1] 253.1065
glance(modG)$AIC
## [1] 190.4613
glance(modH)$AIC
## [1] 178.1737

Because the best model have the lowest AIC, so the best model is modH and second best model is modG.

2c)

Create the coefficient plot associated with your best and second best models. How many coefficients are statistically significant in each model?

You should use the coefplot::coefplot() function to create the plots.

SOLUTION

Add your code chunks here.

library(coefplot)
coefplot(modH)

coefplot(modG)

In the best modelH, there are 3 statistically significants.

In the second best model G, there are 4 statistically significants.

Problem 03

Now that you have an idea about the relationships, it’s time to consider a more detailed view of the uncertainty by fitting Bayesian logistic regression models. You defined log-posterior functions for linear models in previous assignments. You worked with simple linear relationships, interactions, polynomials, and more complex spline basis features. In lecture, we discussed how the linear model framework can be generalized to handle non-continuous binary outcomes. The likelihood changed from a Gaussian to a Binomial distribution and a non-linear link function is required. In this way, the regression model is applied to a linear predictor which “behaves” like the trend in an ordinary linear model. In this problem, you will define the log-posterior function for logistic regression. By doing so you will be able to directly contrast what you did to define the log-posterior function for the linear model in previous assignments.

3a)

The complete probability model for logistic regression consists of the likelihood of the response \(y_n\) given the event probability \(\mu_n\), the inverse link function between the probability of the event, \(\mu_n\), and the linear predictor, \(\eta_n\), and the prior on all linear predictor model coefficients \(\boldsymbol{\beta}\).

As in lecture, you will assume that the \(\boldsymbol{\beta}\)-parameters are a-priori independent Gaussians with a shared prior mean \(\mu_{\beta}\) and a shared prior standard deviation \(\tau_{\beta}\).

Write out complete probability model for logistic regression. You must write out the \(n\)-th observation’s linear predictor using the inner product of the \(n\)-th row of a design matrix \(\mathbf{x}_{n,:}\) and the unknown \(\boldsymbol{\beta}\)-parameter column vector. You can assume that the number of unknown coefficients is equal to \(D + 1\).

You are allowed to separate each equation into its own equation block.

HINT: The “given” sign, the vertical line, \(\mid\), is created by typing \mid in a latex math expression. The product symbol (the giant PI) is created with prod_{}^{}.

SOLUTION

Add your equation blocks here. \[ P \left( y_n | \mu_n \right) = \mu_n^\left( y_n \right) \left( 1 - \mu_n \right)^ \left( 1- y_n \right) \] \[ \mu_n = logit^\left( -1 \right) \left( -η_n \right) \]

\[ η_n = X_n,:\beta \]

3b)

You will fit 8 Bayesian logistic regression models using the same linear predictor trend expressions that you used in the non-Bayesian logistic regression models. You will program the log-posterior function in the same style as the linear model log-posterior functions. This allows you to use the same Laplace Approximation strategy to execute the Bayesian inference.

You must first define the design matrices for each of the 8 models. You must name the design matrices Xmat_A through Xmat_H. As a reminder, the 8 models are provided below.

A: Categorical input only
B: Linear additive features using the continuous inputs only
C: Linear additive features using all inputs (categorical and continuous)
D: Interact the categorical input with the continuous inputs
E: Add the categorical input to linear main effects and interaction between the continuous inputs
F: Interact the categorical input with main effect and interaction features associated with the continuous inputs
G: Add the categorical input to the linear main continuous effects, interaction between continuous, and quadratic continuous features
H: Interact the categorical input to the linear main continuous effects, interaction between continuous, and quadratic continuous features

SOLUTION

Create the design matrices for the 8 models. Add your code chunks below.

Xmat_A <- model.matrix(~ x1, data = df1)

Xmat_B <- model.matrix(~ x2 + x3, data = df1)

Xmat_C <- model.matrix(~ x1 + x2 + x3, data = df1)

Xmat_D <- model.matrix(~ x1 * (x2 + x3), data = df1)

Xmat_E <- model.matrix(~ x1 + x2 * x3, data = df1)

Xmat_F <- model.matrix(~ x1 * (x2 * x3), data = df1)

Xmat_G <- model.matrix(~ x1 + (x2 * x3 + I(x2^2) + I(x3^2)), data = df1)

Xmat_H <- model.matrix(~ x1 * (x2 * x3 + I(x2^2) + I(x3^2)), data = df1)

3c)

The log-posterior function you will program requires the design matrix, the observed output vector, and the prior specification. In previous assignments, you provided that information with a list. You will do the same thing in this assignment. The code chunk below is started for you. The lists follow the same naming convention as the design matrices. The info_A list corresponds to the information for model A, while info_H corresponds to the information for model H. You must assign the design matrix to the corresponding list and complete the rest of the required information. The observed binary outcome vector must be assigned to the yobs field. The prior mean and prior standard deviation must be assigned to the mu_beta and tau_beta fields, respectively.

Complete the code chunk below by completing the lists of required for each model. The list names are consistent with the design matrix names you defined in the previous problem. You must use a prior mean of 0 and a prior standard deviation of 4.5.

SOLUTION

info_A <- list(
  yobs = df1$y,
  design_matrix = Xmat_A,
  mu_beta = 0,
  tau_beta = 4.5
)

info_B <- list(
  yobs = df1$y,
  design_matrix = Xmat_B,
  mu_beta = 0,
  tau_beta = 4.5
)

info_C <- list(
  yobs = df1$y,
  design_matrix = Xmat_C,
  mu_beta = 0,
  tau_beta = 4.5
)

info_D <- list(
  yobs = df1$y,
  design_matrix = Xmat_D,
  mu_beta = 0,
  tau_beta = 4.5
)

info_E <- list(
  yobs = df1$y,
  design_matrix = Xmat_E,
  mu_beta = 0,
  tau_beta = 4.5
)

info_F <- list(
  yobs = df1$y,
  design_matrix = Xmat_F,
  mu_beta = 0,
  tau_beta = 4.5
)

info_G <- list(
  yobs = df1$y,
  design_matrix = Xmat_G,
  mu_beta = 0,
  tau_beta = 4.5
)

info_H <- list(
  yobs = df1$y,
  design_matrix = Xmat_H,
  mu_beta = 0,
  tau_beta = 4.5
)

3d)

You will now define the log-posterior function for logistic regression, logistic_logpost(). The first argument to logistic_logpost() is the vector of unknowns and the second argument is the list of required information. You will assume that the variables within the my_info list are those contained in the info_A through info_H lists you defined previously.

Complete the code chunk to define the logistic_logpost() function. The comments describe what you need to fill in. Do you need to separate out the \(\boldsymbol{\beta}\)-parameters from the vector of unknowns?

After you complete logistic_logpost(), test it by setting the unknowns vector to be a vector of -1’s and then 2’s for the model A case (the model with a only the categorical input). If you have successfully programmed the function you should get -164.6906 and -541.6987 for the -1 test case and +2 test case, respectively.

SOLUTION

Do you need to separate the \(\boldsymbol{\beta}\)-parameters from the unknowns vector?
No, I don’t need to separate the \(\boldsymbol{\beta}\)-parameters from the unknowns vector.

logistic_logpost <- function(unknowns, my_info)
{
  # extract the design matrix and assign to X
  X <- my_info$design_matrix
  
  # calculate the linear predictor
  eta <- X %*% unknowns
  
  # calculate the event probability
  mu <- boot::inv.logit(eta)
  
  # evaluate the log-likelihood
  log_lik <- sum(my_info$yobs * log(mu) + (1 - my_info$yobs) * log(1 - mu))
  
  # evaluate the log-prior
  log_prior <- sum(dnorm(unknowns, mean = my_info$mu_beta, sd = my_info$tau_beta, log = TRUE))
  
  # sum together
  return(log_lik + log_prior)
}

Test out your function using the info_A information and setting the unknowns to a vector of -1’s.

###
logistic_logpost(rep(-1, ncol(Xmat_A)), info_A)
## [1] -164.6906

Test out your function using the info_A information and setting the unknowns to a vector of 2’s.

###
logistic_logpost(rep(2, ncol(Xmat_A)), info_A)
## [1] -541.6987

3e)

The my_laplace() function is provided to you in the code chunk below.

my_laplace <- function(start_guess, logpost_func, ...)
{
  # code adapted from the `LearnBayes`` function `laplace()`
  fit <- optim(start_guess,
               logpost_func,
               gr = NULL,
               ...,
               method = "BFGS",
               hessian = TRUE,
               control = list(fnscale = -1, maxit = 5001))
  
  mode <- fit$par
  post_var_matrix <- -solve(fit$hessian)
  p <- length(mode)
  int <- p/2 * log(2 * pi) + 0.5 * log(det(post_var_matrix)) + logpost_func(mode, ...)
  # package all of the results into a list
  list(mode = mode,
       var_matrix = post_var_matrix,
       log_evidence = int,
       converge = ifelse(fit$convergence == 0,
                         "YES", 
                         "NO"),
       iter_counts = as.numeric(fit$counts[1]))
}

You will use my_laplace() to execute the Laplace Approximation for all 8 models. You must use an initial guess of zero for all unknowns for each model.

Perform the Laplace Approximation for all 8 models. Assign the results to the laplace_A through laplace_H objects. The names should be consistent with the design matrices and lists of required information. Thus, laplace_A must correspond to the info_A and Xmat_A objects.

Should you be concerned that the initial guess will impact the results?

SOLUTION

Does the initial guess matter?
Yes. According to Laplace results, the initial guess will impact the results.

Add the code chunks here.

laplace_A <- my_laplace(rep(0, ncol(Xmat_A)), logistic_logpost, info_A)
laplace_A
## $mode
## [1] -0.55500941 -0.84527527  0.04408551
## 
## $var_matrix
##             [,1]        [,2]        [,3]
## [1,]  0.05782617 -0.05757377 -0.05767426
## [2,] -0.05757377  0.14570798  0.05742253
## [3,] -0.05767426  0.05742253  0.11071731
## 
## $log_evidence
## [1] -145.3736
## 
## $converge
## [1] "YES"
## 
## $iter_counts
## [1] 18
laplace_B <- my_laplace(rep(0, ncol(Xmat_B)), logistic_logpost, info_B)
laplace_B
## $mode
## [1] -0.8778061  0.5823639 -0.1492538
## 
## $var_matrix
##              [,1]         [,2]         [,3]
## [1,]  0.023897929 -0.006483630  0.001546943
## [2,] -0.006483630  0.024109873 -0.001991444
## [3,]  0.001546943 -0.001991444  0.022148352
## 
## $log_evidence
## [1] -142.4161
## 
## $converge
## [1] "YES"
## 
## $iter_counts
## [1] 35
laplace_C <- my_laplace(rep(0, ncol(Xmat_C)), logistic_logpost, info_C)
laplace_C
## $mode
## [1] -0.7130612 -0.9261202  0.1926824  0.6475370 -0.1832697
## 
## $var_matrix
##               [,1]         [,2]          [,3]         [,4]          [,5]
## [1,]  0.0665238839 -0.061381852 -0.0657344204 -0.008569389  0.0007614299
## [2,] -0.0613818522  0.161514564  0.0617490669 -0.006868123  0.0047607662
## [3,] -0.0657344204  0.061749067  0.1220206295  0.006626974 -0.0002383668
## [4,] -0.0085693895 -0.006868123  0.0066269738  0.027199560 -0.0028668110
## [5,]  0.0007614299  0.004760766 -0.0002383668 -0.002866811  0.0235298623
## 
## $log_evidence
## [1] -142.8198
## 
## $converge
## [1] "YES"
## 
## $iter_counts
## [1] 25
laplace_D <- my_laplace(rep(0, ncol(Xmat_D)), logistic_logpost, info_D)
laplace_D
## $mode
## [1] -0.6420239 -0.9059754 -0.0853263  0.4026794 -0.1013667 -0.2991826  1.6410969
## [8] -0.4699179 -0.2012216
## 
## $var_matrix
##                [,1]          [,2]          [,3]          [,4]          [,5]
##  [1,]  6.461174e-02 -6.424703e-02 -6.428363e-02 -0.0164466867  1.037720e-05
##  [2,] -6.424703e-02  1.747682e-01  6.392092e-02  0.0163143200  8.208691e-05
##  [3,] -6.428363e-02  6.392092e-02  1.541192e-01  0.0162218156  1.444089e-05
##  [4,] -1.644669e-02  1.631432e-02  1.622182e-02  0.0615372131 -7.719003e-04
##  [5,]  1.037720e-05  8.208691e-05  1.444089e-05 -0.0007719003  4.499597e-02
##  [6,]  1.634376e-02 -2.961592e-02 -1.611994e-02 -0.0613011689  7.640379e-04
##  [7,]  1.608822e-02 -1.595859e-02 -6.563896e-02 -0.0607478742  6.481087e-04
##  [8,]  1.234753e-04  4.114239e-02 -1.474668e-04  0.0007273425 -4.475768e-02
##  [9,]  6.434099e-05 -1.557677e-04  1.020623e-02  0.0006027858 -4.474318e-02
##                [,6]          [,7]          [,8]          [,9]
##  [1,]  0.0163437645  0.0160882190  0.0001234753  6.434099e-05
##  [2,] -0.0296159234 -0.0159585905  0.0411423917 -1.557677e-04
##  [3,] -0.0161199358 -0.0656389646 -0.0001474668  1.020623e-02
##  [4,] -0.0613011689 -0.0607478742  0.0007273425  6.027858e-04
##  [5,]  0.0007640379  0.0006481087 -0.0447576845 -4.474318e-02
##  [6,]  0.1351301346  0.0605149682 -0.0029845015 -5.956198e-04
##  [7,]  0.0605149682  0.3057683029 -0.0006050266 -5.196492e-02
##  [8,] -0.0029845015 -0.0006050266  0.1517121534  4.450640e-02
##  [9,] -0.0005956198 -0.0519649193  0.0445063996  1.573744e-01
## 
## $log_evidence
## [1] -142.795
## 
## $converge
## [1] "YES"
## 
## $iter_counts
## [1] 45
laplace_E <- my_laplace(rep(0, ncol(Xmat_E)), logistic_logpost, info_E)
laplace_E
## $mode
## [1] -0.713096877 -0.927277305  0.190826126  0.648567377 -0.187250968
## [6]  0.008418843
## 
## $var_matrix
##              [,1]          [,2]         [,3]         [,4]         [,5]
## [1,]  0.066658870 -0.0612855134 -0.065475022 -0.008794406  0.001694410
## [2,] -0.061285513  0.1615041800  0.061803268 -0.006961476  0.005057761
## [3,] -0.065475022  0.0618032684  0.122397329  0.006291223  0.001068750
## [4,] -0.008794406 -0.0069614759  0.006291223  0.027503104 -0.004118232
## [5,]  0.001694410  0.0050577607  0.001068750 -0.004118232  0.028327668
## [6,] -0.001952447 -0.0007104004 -0.003097788  0.002765858 -0.011017949
##               [,6]
## [1,] -0.0019524473
## [2,] -0.0007104004
## [3,] -0.0030977883
## [4,]  0.0027658582
## [5,] -0.0110179492
## [6,]  0.0252604119
## 
## $log_evidence
## [1] -146.1622
## 
## $converge
## [1] "YES"
## 
## $iter_counts
## [1] 30
laplace_F <- my_laplace(rep(0, ncol(Xmat_F)), logistic_logpost, info_F)
laplace_F
## $mode
##  [1] -0.64054553 -0.93744671 -0.45540006  0.39455027 -0.06102485 -0.06639433
##  [7] -0.40619290  2.06065821 -0.49547660 -0.56563937 -0.30992586  1.49133467
## 
## $var_matrix
##                [,1]          [,2]          [,3]         [,4]          [,5]
##  [1,]  6.477776e-02 -0.0644196802 -0.0641691500 -0.016657694 -5.969345e-05
##  [2,] -6.441968e-02  0.1778392920  0.0638148157  0.016578954  1.560934e-04
##  [3,] -6.416915e-02  0.0638148157  0.2157735007  0.016053981  5.504831e-04
##  [4,] -1.665769e-02  0.0165789539  0.0160539807  0.062773924 -3.740788e-03
##  [5,] -5.969345e-05  0.0001560934  0.0005504831 -0.003740788  6.333726e-02
##  [6,] -1.378483e-03  0.0013594571  0.0008631379  0.005750213 -2.902344e-02
##  [7,]  1.660234e-02 -0.0111016599 -0.0160012309 -0.062507379  3.746632e-03
##  [8,]  1.587919e-02 -0.0158053324 -0.1510459350 -0.061329526  2.866238e-03
##  [9,]  1.717206e-04  0.0397542520 -0.0006592057  0.003745951 -6.299027e-02
## [10,]  4.429181e-04 -0.0005364082  0.0787322386  0.003154390 -6.260898e-02
## [11,]  1.412589e-03  0.0177517052 -0.0008995948 -0.005662040  2.890388e-02
## [12,]  6.681455e-04 -0.0006536842 -0.1516025892 -0.004739626  2.797305e-02
##                [,6]         [,7]         [,8]          [,9]         [,10]
##  [1,] -0.0013784834  0.016602339  0.015879194  0.0001717206  0.0004429181
##  [2,]  0.0013594571 -0.011101660 -0.015805332  0.0397542520 -0.0005364082
##  [3,]  0.0008631379 -0.016001231 -0.151045935 -0.0006592057  0.0787322386
##  [4,]  0.0057502126 -0.062507379 -0.061329526  0.0037459512  0.0031543902
##  [5,] -0.0290234419  0.003746632  0.002866238 -0.0629902746 -0.0626089847
##  [6,]  0.0471101796 -0.005693297 -0.004864354  0.0288720484  0.0284672458
##  [7,] -0.0056932966  0.148432005  0.061069232  0.0149566955 -0.0031626587
##  [8,] -0.0048643542  0.061069232  0.459341029 -0.0028763508 -0.1497496191
##  [9,]  0.0288720484  0.014956695 -0.002876351  0.1765503789  0.0622662067
## [10,]  0.0284672458 -0.003162659 -0.149749619  0.0622662067  0.2346278324
## [11,] -0.0469033866  0.031234728  0.004780985 -0.0247668853 -0.0283503579
## [12,] -0.0458072572  0.004687500  0.241122192 -0.0278277834 -0.1397768992
##               [,11]         [,12]
##  [1,]  0.0014125891  0.0006681455
##  [2,]  0.0177517052 -0.0006536842
##  [3,] -0.0008995948 -0.1516025892
##  [4,] -0.0056620399 -0.0047396261
##  [5,]  0.0289038793  0.0279730544
##  [6,] -0.0469033866 -0.0458072572
##  [7,]  0.0312347284  0.0046874997
##  [8,]  0.0047809849  0.2411221919
##  [9,] -0.0247668853 -0.0278277834
## [10,] -0.0283503579 -0.1397768992
## [11,]  0.1354739225  0.0456065767
## [12,]  0.0456065767  0.5020176184
## 
## $log_evidence
## [1] -146.7974
## 
## $converge
## [1] "YES"
## 
## $iter_counts
## [1] 43
laplace_G <- my_laplace(rep(0, ncol(Xmat_G)), logistic_logpost, info_G)
laplace_G
## $mode
## [1] -2.24211646 -1.17013199  0.73250335  1.23041793 -0.17999923 -0.37616045
## [7]  1.65803983 -0.07557434
## 
## $var_matrix
##              [,1]         [,2]         [,3]         [,4]          [,5]
## [1,]  0.189500164 -0.074525593 -0.133985873 -0.039890787 -0.0019834901
## [2,] -0.074525593  0.279354476  0.100403368 -0.019048514  0.0121451878
## [3,] -0.133985873  0.100403368  0.196973236  0.019499636  0.0034951884
## [4,] -0.039890787 -0.019048514  0.019499636  0.091855978 -0.0119084454
## [5,] -0.001983490  0.012145188  0.003495188 -0.011908445  0.0449336866
## [6,] -0.009614663 -0.008941243 -0.003350858 -0.035151199  0.0033799776
## [7,] -0.066990332 -0.028884337  0.022213878  0.041566237 -0.0001574764
## [8,]  0.001315817  0.011489333  0.002031697  0.007928597 -0.0005461659
##              [,6]          [,7]          [,8]
## [1,] -0.009614663 -0.0669903322  0.0013158167
## [2,] -0.008941243 -0.0288843373  0.0114893328
## [3,] -0.003350858  0.0222138775  0.0020316974
## [4,] -0.035151199  0.0415662369  0.0079285973
## [5,]  0.003379978 -0.0001574764 -0.0005461659
## [6,]  0.044173814 -0.0137683238 -0.0111489788
## [7,] -0.013768324  0.0758728159 -0.0065776527
## [8,] -0.011148979 -0.0065776527  0.0529434439
## 
## $log_evidence
## [1] -110.3315
## 
## $converge
## [1] "YES"
## 
## $iter_counts
## [1] 34
laplace_H <- my_laplace(rep(0, ncol(Xmat_H)), logistic_logpost, info_H)
laplace_H
## $mode
##  [1] -2.1454168 -1.3572839 -0.3840456  0.2739202  0.1653047 -0.1850853
##  [7]  1.6594146 -0.2328253  0.6285875  3.5960152 -0.1503919 -1.1915626
## [13] -0.8180670 -0.4238694  0.6598131  0.2329400 -0.2978803  1.6272778
## 
## $var_matrix
##              [,1]        [,2]        [,3]        [,4]        [,5]         [,6]
##  [1,]  0.28737137 -0.27263595 -0.27893291 -0.02585323 -0.03539902 -0.069279941
##  [2,] -0.27263595  1.03889567  0.26470577  0.02270512  0.03337041  0.067024582
##  [3,] -0.27893291  0.26470577  0.72977802  0.02048907  0.03614957  0.069174393
##  [4,] -0.02585323  0.02270512  0.02048907  0.16108623 -0.02787346 -0.025040656
##  [5,] -0.03539902  0.03337041  0.03614957 -0.02787346  0.14409083  0.020745549
##  [6,] -0.06927994  0.06702458  0.06917439 -0.02504066  0.02074555  0.105356404
##  [7,] -0.14670026  0.13647392  0.14091557  0.02113032  0.02522426 -0.006277128
##  [8,]  0.02443811 -0.02262122 -0.02642823  0.03284255 -0.01425265 -0.032223910
##  [9,]  0.02083832 -0.24469533 -0.01571868 -0.15780851  0.02850188  0.024860539
## [10,]  0.01875867 -0.01615226 -0.49729607 -0.14693281  0.02323773  0.019960945
## [11,]  0.03379769 -0.08805567 -0.03460809  0.02845795 -0.14286437 -0.020651147
## [12,]  0.03697682 -0.03481538  0.10524898  0.02338160 -0.14111742 -0.018950525
## [13,]  0.07021116 -0.04326855 -0.07002623  0.02379584 -0.02078890 -0.104011040
## [14,]  0.06815144 -0.06585782 -0.01897086  0.01787390 -0.01749690 -0.100188160
## [15,]  0.13714669 -0.58320208 -0.13172870 -0.01830684 -0.02380910  0.006682197
## [16,]  0.14005509 -0.13025434 -0.41271612 -0.01758563 -0.02538661  0.006192685
## [17,] -0.02345570  0.07084851  0.02546432 -0.03280202  0.01421120  0.032115470
## [18,] -0.02506884  0.02315705 -0.21244945 -0.02326502  0.01078729  0.027195922
##               [,7]        [,8]        [,9]       [,10]        [,11]       [,12]
##  [1,] -0.146700260  0.02443811  0.02083832  0.01875867  0.033797688  0.03697682
##  [2,]  0.136473919 -0.02262122 -0.24469533 -0.01615226 -0.088055675 -0.03481538
##  [3,]  0.140915574 -0.02642823 -0.01571868 -0.49729607 -0.034608091  0.10524898
##  [4,]  0.021130323  0.03284255 -0.15780851 -0.14693281  0.028457953  0.02338160
##  [5,]  0.025224258 -0.01425265  0.02850188  0.02323773 -0.142864365 -0.14111742
##  [6,] -0.006277128 -0.03222391  0.02486054  0.01996095 -0.020651147 -0.01895052
##  [7,]  0.185413437 -0.03622093 -0.01700234 -0.01495715 -0.023905053 -0.02673585
##  [8,] -0.036220934  0.17614692 -0.03284389 -0.02365978  0.014284551  0.01170923
##  [9,] -0.017002341 -0.03284389  0.49885620  0.14404991  0.046856167 -0.02411828
## [10,] -0.014957152 -0.02365978  0.14404991  1.46302948 -0.023750245 -0.41979505
## [11,] -0.023905053  0.01428455  0.04685617 -0.02375024  0.307707032  0.13988408
## [12,] -0.026735853  0.01170923 -0.02411828 -0.41979505  0.139884078  0.47878167
## [13,]  0.004225943  0.03243871 -0.12777295 -0.01888634 -0.001968352  0.01904491
## [14,]  0.005397174  0.02581418 -0.01782654 -0.65090521  0.017389740  0.23037481
## [15,] -0.177601725  0.03507784  0.23266188  0.01258802  0.094355903  0.02520446
## [16,] -0.179675805  0.03575655  0.01365397  0.34035130  0.024117744 -0.05546027
## [17,]  0.035206617 -0.17477740  0.03188224  0.02363991  0.007118521 -0.01167208
## [18,]  0.036179130 -0.16289845  0.02344801  0.80905063 -0.010790506 -0.19634657
##              [,13]        [,14]        [,15]        [,16]        [,17]
##  [1,]  0.070211156  0.068151436  0.137146687  0.140055087 -0.023455702
##  [2,] -0.043268549 -0.065857823 -0.583202081 -0.130254336  0.070848506
##  [3,] -0.070026232 -0.018970861 -0.131728695 -0.412716119  0.025464323
##  [4,]  0.023795844  0.017873895 -0.018306842 -0.017585634 -0.032802017
##  [5,] -0.020788904 -0.017496895 -0.023809098 -0.025386611  0.014211203
##  [6,] -0.104011040 -0.100188160  0.006682197  0.006192685  0.032115470
##  [7,]  0.004225943  0.005397174 -0.177601725 -0.179675805  0.035206617
##  [8,]  0.032438711  0.025814182  0.035077841  0.035756553 -0.174777401
##  [9,] -0.127772945 -0.017826538  0.232661884  0.013653966  0.031882240
## [10,] -0.018886340 -0.650905208  0.012588023  0.340351302  0.023639911
## [11,] -0.001968352  0.017389740  0.094355903  0.024117744  0.007118521
## [12,]  0.019044913  0.230374813  0.025204462 -0.055460275 -0.011672082
## [13,]  0.362694884  0.098932170 -0.175520953 -0.004218263  0.018980162
## [14,]  0.098932170  0.768286727 -0.005891811 -0.094952875 -0.025738215
## [15,] -0.175520953 -0.005891811  0.613983859  0.172178699 -0.077172811
## [16,] -0.004218263 -0.094952875  0.172178699  0.547119152 -0.034774618
## [17,]  0.018980162 -0.025738215 -0.077172811 -0.034774618  0.326471545
## [18,] -0.027487483 -0.524459282 -0.034917593 -0.057910610  0.161610795
##             [,18]
##  [1,] -0.02506884
##  [2,]  0.02315705
##  [3,] -0.21244945
##  [4,] -0.02326502
##  [5,]  0.01078729
##  [6,]  0.02719592
##  [7,]  0.03617913
##  [8,] -0.16289845
##  [9,]  0.02344801
## [10,]  0.80905063
## [11,] -0.01079051
## [12,] -0.19634657
## [13,] -0.02748748
## [14,] -0.52445928
## [15,] -0.03491759
## [16,] -0.05791061
## [17,]  0.16161080
## [18,]  1.44752403
## 
## $log_evidence
## [1] -112.7092
## 
## $converge
## [1] "YES"
## 
## $iter_counts
## [1] 54

3f)

The laplace_A object is the Bayesian version of modA that you fit previously in Problem 2a). Let’s compare the Bayesian result to the non-Bayesian result.

Calculate the 95% confidence interval on the regression coefficients associated with laplace_A and dispaly the interval bounds to the screen. Which features are statistically significant according to the Bayesian model? Are the results consistent with the non-Bayesian model, modA?

SOLUTION

Add your code chunks here.

c(laplace_A $mode[1] - 2 * sqrt(diag(laplace_A $var_matrix)[1]),
  laplace_A $mode[1] + 2 * sqrt(diag(laplace_A $var_matrix)[1]))
## [1] -1.03595084 -0.07406797
c(laplace_A $mode[2] - 2 * sqrt(diag(laplace_A $var_matrix)[2]),
  laplace_A $mode[2] + 2 * sqrt(diag(laplace_A $var_matrix)[2]))
## [1] -1.60870957 -0.08184096
c(laplace_A $mode[3] - 2 * sqrt(diag(laplace_A $var_matrix)[3]),
  laplace_A $mode[3] + 2 * sqrt(diag(laplace_A $var_matrix)[3]))
## [1] -0.6213987  0.7095697

The third feature(x1C) is statistically significant.

summary(modA)
## 
## Call:
## glm(formula = y ~ x1, family = "binomial", data = df1)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -0.9695  -0.9528  -0.6628   1.4006   1.8020  
## 
## Coefficients:
##             Estimate Std. Error z value Pr(>|z|)  
## (Intercept) -0.55431    0.24148  -2.295   0.0217 *
## x1B         -0.84968    0.38378  -2.214   0.0268 *
## x1C          0.04349    0.33414   0.130   0.8965  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 280.56  on 224  degrees of freedom
## Residual deviance: 273.46  on 222  degrees of freedom
## AIC: 279.46
## 
## Number of Fisher Scoring iterations: 4

Yes, the result is same.

3g)

You trained 8 Bayesian logistic regression models. Let’s identify the best using the Evidence based approach!

Calculate the posterior model weight associated with each of the 8 models. Create a bar chart that shows the posterior model weight for each model. The models should be named 'A' through 'H'.

SOLUTION

Add your code chunks here.

log_evidence <- c(laplace_A$log_evidence, laplace_B$log_evidence, laplace_C$log_evidence, laplace_D$log_evidence, laplace_E$log_evidence, laplace_F$log_evidence, laplace_G$log_evidence, laplace_H$log_evidence)

post_model_weight_unnormalized <- exp(log_evidence - max(log_evidence))

post_model_weight <- post_model_weight_unnormalized / sum(post_model_weight_unnormalized)

post_model_weight
## [1] 5.532077e-16 1.064887e-14 7.111529e-15 7.290534e-15 2.514279e-16
## [6] 1.332109e-16 9.151129e-01 8.488709e-02
model_names <- c('A','B','C','D','E','F','G','H')

df_post_model_weights <- data.frame(Model = model_names, Weight = post_model_weight)

ggplot(df_post_model_weights, aes(x = Model, y = Weight)) +
  geom_bar(stat = "identity") +
  theme_minimal()

3h)

Is your Bayesian identified best model consistent with the non-Bayesian identified best model?

SOLUTION

What do you think?
Yes, they have the same result. The best model is G and the second best model is H.

Problem 04

You trained multiple models ranging from simple to complex. You identified the best model using several approaches. It is now time to examine the predictive trends of the models to better interpret their behavior. You will not predict the training set to study the trends. Instead, you will visualize the trends on a specifically designed prediction grid. The code chunk below defines that grid for you using the expand.grid() function. If you look closely, the x3 variable has 51 evenly spaced points between -3 and 3. The x1 variable has 9 unique values evenly spaced between -3 and 3. These lower and upper bounds are roughly consistent with the training set bounds. The x1 variable consists of the 3 unique values present in the training set. The expand.grid() function creates the full-factorial combination of the 3 variables.

viz_grid <- expand.grid(x1 = unique(df1$x1),
                        x2 = seq(-3, 3, length.out = 9),
                        x3 = seq(-3, 3, length.out = 51),
                        KEEP.OUT.ATTRS = FALSE,
                        stringsAsFactors = FALSE) %>% 
  as.data.frame() %>% tibble::as_tibble()

viz_grid %>% glimpse()
## Rows: 1,377
## Columns: 3
## $ x1 <chr> "C", "B", "A", "C", "B", "A", "C", "B", "A", "C", "B", "A", "C", "B…
## $ x2 <dbl> -3.00, -3.00, -3.00, -2.25, -2.25, -2.25, -1.50, -1.50, -1.50, -0.7…
## $ x3 <dbl> -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3,…

The glimpse provided above shows there are 1377 combinations of the 3 inputs. You will therefore make over 1300 predictions to study the trends of the event probability!

4a)

As with previous linear model assignments, you must first generate random posterior samples of the unknown parameters from the Laplace Approximation assumed Multivariate Normal (MVN) distribution. Although you were able to apply the my_laplace() function to both the regression and logistic regression settings, you cannot directly apply the generate_lm_post_samples() function from your previous assignments. You will therefore adapt generate_lm_post_samples() and define generate_glm_post_samples(). The code chunk below starts the function for you and uses just two input arguments, mvn_result and num_samples. You must complete the function.

Why can you not directly use the generate_lm_post_samples() function? Since the length_beta argument is NOT provided to generate_glm_post_samples(), how can you determine the number of \(\boldsymbol{\beta}\)-parameters? Complete the code chunk below by first assigning the number of \(\boldsymbol{\beta}\)-parameters to the length_beta variable. Then generate the random samples from the MVN distribution. You do not have to name the variables, you only need to call the correct random number generator.

SOLUTION

What do you think? Why do we need a new function compared to the previous assignments?

generate_glm_post_samples <- function(mvn_result, num_samples)
{
  # specify the number of unknown beta parameters
  length_beta <- length(mvn_result$mode)
  
  # generate the random samples
  beta_samples <- MASS::mvrnorm(n = num_samples, mu = mvn_result$mode, Sigma = mvn_result$var_matrix)
  
  # change the data type and name
  beta_samples %>% 
    as.data.frame() %>% tibble::as_tibble() %>% 
    purrr::set_names(sprintf("beta_%02d", (1:length_beta) - 1))
}

4b)

You will now define a function which calculates the posterior samples on the linear predictor and the event probability. The function, post_logistic_pred_samples() is started for you in the code chunk below. It consists of two input arguments Xnew and Bmat. Xnew is a test design matrix where rows correspond to prediction points. The matrix Bmat stores the posterior samples on the \(\boldsymbol{\beta}\)-parameters, where each row is a posterior sample and each column is a parameter.

Complete the code chunk below by using matrix math to calculate the posterior samples of the linear predictor. Then, calculate the posterior smaples of the event probability.

The eta_mat and mu_mat matrices are returned within a list, similar to how the Umat and Ymat matrices were returned for the regression problems.

HINT: The boot::inv.logit() can take a matrix as an input. When it does, it returns a matrix as a result.

SOLUTION

post_logistic_pred_samples <- function(Xnew, Bmat)
{
  # calculate the linear predictor at all prediction points and posterior samples
  eta_mat <- Xnew %*% t(Bmat)
  
  # calculate the event probability
  mu_mat <-boot::inv.logit(eta_mat)
  
  # book keeping
  list(eta_mat = eta_mat, mu_mat = mu_mat)
}

4c)

The code chunk below defines a function summarize_logistic_pred_from_laplace() which manages the actions necessary to summarize posterior predictions of the event probability. The first argument, mvn_result, is the Laplace Approximation object. The second object is the test design matrix, Xtest, and the third argument, num_samples, is the number of posterior samples to make. You must follow the comments within the function in order to generate posterior prediction samples of the linear predictor and the event probability, and then to summarize the posterior predictions of the event probability.

The result from summarize_logistic_pred_from_laplace() summarizes the posterior predicted event probability with the posterior mean, as well as the 0.05 and 0.95 Quantiles. If you have completed the post_logistic_pred_samples() function correctly, the dimensions of the mu_mat matrix should be consistent with those from the Umat matrix from the regression problems.

The posterior summary statistics summarize the posterior samples. You must therefore choose between colMeans() and rowMeans() as to how to calculate the posterior mean event probability for each prediction point. The posterior Quantiles are calculated for you.

Follow the comments in the code chunk below to complete the definition of the summarize_logistic_pred_from_laplace() function. You must generate posterior samples, make posterior predictions, and then summarize the posterior predictions of the event probability.

HINT: The result from post_logistic_pred_samples() is a list.

SOLUTION

summarize_logistic_pred_from_laplace <- function(mvn_result, Xtest, num_samples)
{
  # generate posterior samples of the beta parameters
  betas <- generate_glm_post_samples(mvn_result, num_samples)
  
  # data type conversion
  betas <- as.matrix(betas)
  
  # make posterior predictions on the test set
  pred_test <- post_logistic_pred_samples(Xtest, betas)
  
  # calculate summary statistics on the posterior predicted probability
  # summarize over the posterior samples
  
  # posterior mean, should you summarize along rows (rowMeans) or 
  # summarize down columns (colMeans) ???
  mu_avg <- pred_test$mu_mat %>% rowMeans()
  
  # posterior quantiles
  mu_q05 <- apply(pred_test$mu_mat, 1, stats::quantile, probs = 0.05)
  mu_q95 <- apply(pred_test$mu_mat, 1, stats::quantile, probs = 0.95)
  
  # book keeping
  tibble::tibble(
    mu_avg = mu_avg,
    mu_q05 = mu_q05,
    mu_q95 = mu_q95
  ) %>% 
    tibble::rowid_to_column("pred_id")
}

4d)

You will not make predictions from all 8 models that you previously trained. Instead, you will focus on model D, model G, and model H.

You must define the vizualization grid design matrices consistent with the model D, model G, and model H formulas. You must name the design matrices Xviz_D, Xviz_G, and Xviz_H. You must create the design matrices using the viz_grid dataframe which was defined at the start of Problem 04.

SOLUTION

Add your code chunks here.

Xviz_D <- model.matrix(~ x1 * (x2 + x3), data = viz_grid)
Xviz_G <- model.matrix(~ x1 + ((x2 * x3) + I(x2^2) + I(x3^2)), data = viz_grid)
Xviz_H <- model.matrix(~ x1 * (x2 * x3 + I(x2^2) + I(x3^2)), data = viz_grid)

4e)

Summarize the posterior predicted event probability associated with the three models on the visualization grid. After making the predictions, a code chunk is provided for you which generates a figure showing how the posterior predicted probability summaries compare with the observed binary outcomes. Which of the three models appear to better capture the trends in the binary outcome?

Call summarize_logistic_pred_from_laplace() for the all three models on the visualization grid. The object names specify which model you should make predictions with. For example, post_pred_summary_D corresponds to the predictions associated with model D. Specify the number of posterior samples to be 2500. Print the dimensions of the resulting objects to the screen. How many rows are in each data set?

SOLUTION

The prediction summarizes should be executed in the code chunk below.

set.seed(8123) 

post_pred_summary_D <- summarize_logistic_pred_from_laplace(laplace_D, Xviz_D, 2500)

post_pred_summary_G <- summarize_logistic_pred_from_laplace(laplace_G, Xviz_G, 2500)

post_pred_summary_H <- summarize_logistic_pred_from_laplace(laplace_H, Xviz_H, 2500)

Print the dimensions of the objects to the screen.

###
dim(post_pred_summary_D)
## [1] 1377    4
dim(post_pred_summary_G)
## [1] 1377    4
dim(post_pred_summary_H)
## [1] 1377    4

4f)

The code chunk below defines a function for you. The function creates a figure which visualizes the posterior predictive summary statistics of the event probability for a single model. The figure is created to focus on the trend with respect to x3. Facets are used to examine the influence of x2. The line color and ribbon aesthetics are used to denote the categorical variable x1. This figure is specific to the three variable names in this assignment, but it shows the basic layout required for visualizing predictive trends from Bayesian logistic regression models with 3 inputs.

viz_bayes_logpost_preds <- function(post_pred_summary, input_df)
{
  post_pred_summary %>% 
    left_join(input_df %>% tibble::rowid_to_column('pred_id'),
              by = 'pred_id') %>% 
    ggplot(mapping = aes(x = x3)) +
    geom_ribbon(mapping = aes(ymin = mu_q05,
                              ymax = mu_q95,
                              group = interaction(x1, x2),
                              fill = x1),
                alpha = 0.25) +
    geom_line(mapping = aes(y = mu_avg,
                            group = interaction(x1, x2),
                            color = x1),
              size = 1.15) +
    facet_wrap( ~ x2, labeller = 'label_both') +
    labs(y = "event probability") +
    theme_bw()
}

Use the viz_bayes_logpost_preds() function to visualize posterior predictive trends of the event probability for the 3 models: model D, model G, and model H.

SOLUTION

Add your code chunks here.

viz_bayes_logpost_preds(post_pred_summary_D, viz_grid)
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.

viz_bayes_logpost_preds(post_pred_summary_G, viz_grid)

viz_bayes_logpost_preds(post_pred_summary_H, viz_grid)

4g)

Describe the differences in the predictive trends between the 3 models?

SOLUTION

What do you think?

Among the three groups of models, model G has the most stable trend and the highest degree of fit.

Problem 05

You should have noticed a pattern associated with the 8 models that you previously fit. The most complex model, model H, contains all other models! It is a super set of all features from the simpler models. An alternative approach to training many models of varying complexity is to train a single complex model and use regularization to “turn off” the unimportant features. This way we can find out if the most complex model can be turned into a simpler model of just the most key features we need!

We discussed in lecture how the Lasso penalty or its Bayesian analog the Double-Exponential prior are capable of turning off the unimportant features. We focused on regression problems but Lasso can also be applied to classification problems! In this problem you will use caret to manage the training, assessment, and tuning of the glmnet elastic net penalized logistic regression model. The code chunk below imports the caret package for you.

library(caret)
## Loading required package: lattice
## 
## Attaching package: 'caret'
## The following object is masked from 'package:purrr':
## 
##     lift

The caret package prefers the binary outcome to be organized as a factor data type compared to an integer. The data set is reformatted for you in the code chunk below. The binary outcome y is converted to a new variable outcome with values 'event' and 'non_event'. The first level is forced to be 'event' to be consistent with the caret preferred structure.

df_caret <- df1 %>% 
  mutate(outcome = ifelse(y == 1, 'event', 'non_event')) %>% 
  mutate(outcome = factor(outcome, levels = c("event", "non_event"))) %>% 
  select(x1, x2, x3, outcome)

df_caret %>% glimpse()
## Rows: 225
## Columns: 4
## $ x1      <chr> "C", "B", "C", "B", "A", "C", "A", "A", "C", "C", "B", "C", "A…
## $ x2      <dbl> 1.682873632, -1.033648456, 0.110854156, 2.032934019, -0.225540…
## $ x3      <dbl> -0.353085685, -0.778102544, 0.757536960, 0.639465847, 0.017483…
## $ outcome <fct> event, non_event, non_event, non_event, non_event, event, even…

5a)

You must specify the resampling scheme that caret will use to train, assess, and tune a model. You used caret in the previous assignment for a regression problem. Here, you are working with a classification problem and so you cannot use the same performance metric as the previous assignment! Although there are multiple classification metrics we could consider, we will focus on Accuracy in this problem.

Specify the resampling scheme to be 10 fold with 3 repeats. Assign the result of the trainControl() function to the my_ctrl object. Specify the primary performance metric to be 'Accuracy' and assign that to the my_metric object.

SOLUTION

Add your code chunks here.

my_ctrl <- trainControl(method = "repeatedcv", number = 10, repeats = 3)
my_metric <- "Accuracy"

5b)

You must train, assess, and tune an elastic model using the default caret tuning grid. In the caret::train() function you must use the formula interface to specify a model consistent with model H. Thus, your model should interact the categorical input to the linear main continuous effects, interaction between continuous, and quadratic continuous features. However, please pay close attention to your formula. The binary outcome is now named outcome and not y. Assign the method argument to 'glmnet' and set the metric argument to my_metric. Even though the inputs were standardized for you, you must also instruct caret to standardize the features by setting the preProcess argument equal to c('center', 'scale'). This will give you practice standardizing inputs. Assign the trControl argument to the my_ctrl object.

Important: The caret::train() function works with the formula interface. Thus, even though you are using glmnet to fit the model, caret does not require you to organize the design matrix as required by glmnet! Thus, you do NOT need to remove the intercept when defining the formula to caret::train()!

Train, assess, and tune the glmnet elastic net model consistent with model H with the defined resampling scheme. Assign the result to the enet_default object and display the result to the screen.

Which tuning parameter combinations are considered to be the best?

Is the best set of tuning parameters more consistent with Lasso or Ridge regression?

SOLUTION

The random seed is set for you for reproducibility.

set.seed(1234)

enet_default <- train(outcome ~ x1 * ((x2 * x3) + I(x2^2) + I(x3^2)),
                      data = df_caret,
                      method = "glmnet",
                      metric = my_metric,
                      preProcess = c("center", "scale"),
                      trControl = my_ctrl)

enet_default
## glmnet 
## 
## 225 samples
##   3 predictor
##   2 classes: 'event', 'non_event' 
## 
## Pre-processing: centered (17), scaled (17) 
## Resampling: Cross-Validated (10 fold, repeated 3 times) 
## Summary of sample sizes: 202, 202, 202, 203, 202, 202, ... 
## Resampling results across tuning parameters:
## 
##   alpha  lambda        Accuracy   Kappa    
##   0.10   0.0004769061  0.8251372  0.5805807
##   0.10   0.0047690605  0.8282993  0.5802313
##   0.10   0.0476906052  0.8072793  0.4935810
##   0.55   0.0004769061  0.8251372  0.5805807
##   0.55   0.0047690605  0.8298748  0.5824205
##   0.55   0.0476906052  0.8042545  0.4773741
##   1.00   0.0004769061  0.8207235  0.5714822
##   1.00   0.0047690605  0.8328393  0.5904347
##   1.00   0.0476906052  0.8147892  0.5081578
## 
## Accuracy was used to select the optimal model using the largest value.
## The final values used for the model were alpha = 1 and lambda = 0.004769061.

x1C:I(x3^2) is the best. It same as Lasso.

5c)

Create a custom tuning grid to further tune the elastic net lambda and alpha tuning parameters.

Create a tuning grid with the expand.grid() function which has two columns named alpha and lambda. The alpha variable should be evenly spaced between 0.1 and 1.0 by increments of 0.1. The lambda variable should have 25 evenly spaced values in the log-space between the minimum and maximum lambda values from the caret default tuning grid. Assign the tuning grid to the enet_grid object.

How many tuning parameter combinations are you trying out? How many total models will be fit assuming the 5-fold with 3-repeat resampling approach?

HINT: The seq() function includes an argument by to specify the increment width.

SOLUTION

Add your code chunks here.

alpha_values <- seq(0.1, 1.0, by = 0.1)
lambda_min <- min(enet_default$results$lambda)
lambda_max <- max(enet_default$results$lambda)
lambda_values <- exp(seq(log(lambda_min), log(lambda_max), length.out = 25))

enet_grid <- expand.grid(alpha = alpha_values, lambda = lambda_values)

nrow(enet_grid)
## [1] 250
total_models <- 3 * 5 * nrow(enet_grid)
total_models
## [1] 3750

How many?
250 models and 3750 parameters

5d)

Train, assess, and tune the elastic net model with the custom tuning grid and assign the result to the enet_tune object. You should specify the arguments to caret::train() consistent with your solution in Problem 5b), except you should also assign enet_grid to the tuneGrid argument.

Do not print the result to the screen. Instead use the default plot method to visualize the resampling results. Assign the xTrans argument to log in the default plot method call. Use the $bestTune field to print the identified best tuning parameter values to the screen. Is the identified best elastic net model more similar to Lasso or Ridge regression?

SOLUTION

The random seed is set for you for reproducibility. You may add more code chunks if you like.

set.seed(1234)

enet_tune <- caret::train(
  outcome ~ x1 * ((x2 * x3) + I(x2^2) + I(x3^2)),
  data = df_caret,
  method = "glmnet",
  metric = my_metric,
  preProcess = c("center", "scale"),
  trControl = my_ctrl,
  tuneGrid = enet_grid
)

plot(enet_tune, xTrans = log)

enet_tune$bestTune
##    alpha     lambda
## 67   0.3 0.01027463

What do you think?
Yes. It is more similar.

5e)

Print the coefficients to the screen for the tuned elastic net model. Which coefficients are non-zero? Has the complex model H been converted to a simpler model?
#### SOLUTION

### add more code chunks if you'd like
coefs <- coef(enet_tune$finalModel, s = enet_tune$bestTune$lambda)
coefs
## 18 x 1 sparse Matrix of class "dgCMatrix"
##                      s1
## (Intercept)  1.10820920
## x1B          0.41747015
## x1C          .         
## x2          -0.34048219
## x3           .         
## I(x2^2)      0.18667087
## I(x3^2)     -1.72475624
## x2:x3        0.05732699
## x1B:x2      -0.08156494
## x1C:x2      -1.18811574
## x1B:x3       0.03232367
## x1C:x3       0.29324198
## x1B:I(x2^2)  0.43908555
## x1C:I(x2^2)  .         
## x1B:I(x3^2) -0.28249893
## x1C:I(x3^2) -0.13713286
## x1B:x2:x3    0.19009508
## x1C:x2:x3   -0.25635626

What do you think?
x1C,x3,x1C:I(x2^2) are non-zero

5f)

Let’s now visualize the predictions of the event probability from the tuned elastic net penalized logistic regression model. All caret trained models make predictions with a predict() function. The first argument is the caret trained object and the second object, newdata, is the new data set to make predictions with. Earlier in the semester in homework 03, you made predictions from caret trained binary classifiers. That assignment discussed that the optional third argument type dictated the “type” of prediction to make. Setting type = 'prob' instructs the predict() function to return the class predicted probabilities.

Complete the code chunk below. You must make predictions on the visualization grid, viz_grid, using the tuned elastic net model enet_tune. Instruct the predict() function to return the probabilities by setting type = 'prob'.

SOLUTION

pred_viz_enet_probs <- predict(enet_tune, newdata = viz_grid, type = 'prob')

5g)

The code chunk below is completed for you. The pred_viz_enet_probs dataframe is column binded to the viz_grid dataframe. The new object, viz_enet_df, provides the class predicted probabilities for each input combination in the visualization grid. A glimpse is printed to the screen. Please not the eval flag is set to eval=FALSE in the code chunk below. You must change eval to eval=TRUE in the chunk options to make sure the code chunk below runs when you knit the markdown.

viz_enet_df <- viz_grid %>% bind_cols(pred_viz_enet_probs)

viz_enet_df %>% glimpse()
## Rows: 1,377
## Columns: 5
## $ x1        <chr> "C", "B", "A", "C", "B", "A", "C", "B", "A", "C", "B", "A", …
## $ x2        <dbl> -3.00, -3.00, -3.00, -2.25, -2.25, -2.25, -1.50, -1.50, -1.5…
## $ x3        <dbl> -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, …
## $ event     <dbl> 0.9999022, 0.8864276, 0.9984171, 0.9999666, 0.9958424, 0.999…
## $ non_event <dbl> 9.779124e-05, 1.135724e-01, 1.582902e-03, 3.336859e-05, 4.15…

The glimpse reveals that the event column stores the predicted event probability. You must visualize the predicted event probability in a manner consistent with the viz_bayes_logpost_preds() function. The caret trained object does not return uncertainty estimates from the glmnet model and so you will not include uncertainty intervals as ribbons. You will visualize the predicted probability as a line (curve) with respect to x3, for each combination of x2 and x1.

Pipe the viz_enet_df object to ggplot(). Map the x aesthetic to the x3 variable and the y aesthetic to the event variable. Add a geom_line() layer and map the color aesthetic to the x1 variable. Manually assign the size to 1.2. Create the facets by including the facet_wrap() function and specify the facets are “functions of” the x2 input.

SOLUTION

Add your code chunk here.

viz_enet_df %>%
  ggplot(aes(x = x3, y = event, color = x1)) +
  geom_line(size = 1.2) +
  facet_wrap(~x2)

5h)

Are the predicted trends from the tuned elastic net model consistent with the behavior visualized by the Bayesian models?

SOLUTION

What do you think?

5i)

Use the caret varImp() function to generate the variable importances associated with the tuned elastic net model. Plot the variable importances via the default plot method.

What is the most important feature?

SOLUTION

Add your code chunk here.

enet_varimp <- varImp(enet_tune)
plot(enet_varimp)

Problem 06

Let’s now train and tune several advanced methods. You will not program these methods from scratch in this assignment. Instead, you will use the caret::train() function to manage the preprocessing, training, and evaluation of the models via resampling.

You will use the default caret tuning grids associated with each of the models. The default tuning may not yield the best possible performance for these models. For example, small neural networks are trained in the default grid to make sure the run time is relatively fast. However, the point is for you to gain experience with the syntax associated with these models to support your work on the final project.

6a)

You will begin by training a neural network via the nnet package. caret will prompt you to install nnet if you do not have it installed already. Please open the R Console to “see” the prompt messages to help with the installation.

You will train a neural network to classify the binary outcome, outcome, with respect to all inputs. You should not interact inputs together. The formula should therefore “look” as if you are using linear additive features. The neural network will attempt to create non-linear relationships for you! Assign the method argument to 'nnet' and set the metric argument to my_metric. You must also instruct caret to standardize the features by setting the preProcess argument equal to c('center', 'scale'). Assign the trControl argument to the my_ctrl object.

You are therefore using the same resampling scheme for the neural network as you did with the elastic net model! This will allow directly comparing the neural network performance to the elastic net model!

Train, assess, and tune the nnet neural network with the defined resampling scheme. Assign the result to the nnet_default object and print the result to the screen. Which tuning parameter combinations are considered to be the best?

IMPORTANT: include the argument trace = FALSE in the caret::train() function call. This will make sure the nnet package does NOT print the optimization iteration results to the screen.

SOLUTIOn

The random seed is set for you for reproducibility. You may add more code chunks if you like.

set.seed(1234)

nnet_default <- train(outcome ~ ., data = df_caret, method = "nnet", trControl = my_ctrl, preProcess = c('center', 'scale'), metric = my_metric, trace = FALSE)

nnet_default
## Neural Network 
## 
## 225 samples
##   3 predictor
##   2 classes: 'event', 'non_event' 
## 
## Pre-processing: centered (4), scaled (4) 
## Resampling: Cross-Validated (10 fold, repeated 3 times) 
## Summary of sample sizes: 202, 202, 202, 203, 202, 202, ... 
## Resampling results across tuning parameters:
## 
##   size  decay  Accuracy   Kappa    
##   1     0e+00  0.6886913  0.1707863
##   1     1e-04  0.6855402  0.1692201
##   1     1e-01  0.7019379  0.2065714
##   3     0e+00  0.7585913  0.4005057
##   3     1e-04  0.7630105  0.4113777
##   3     1e-01  0.8014822  0.5105946
##   5     0e+00  0.7612374  0.4241012
##   5     1e-04  0.7746596  0.4677505
##   5     1e-01  0.8176932  0.5557965
## 
## Accuracy was used to select the optimal model using the largest value.
## The final values used for the model were size = 5 and decay = 0.1.

6b)

Let’s use predictions to understand the behavior of the neural network! Predictions are made consistent with the previously trained elastic net model because caret managed the training of the neural network. Thus, you will use syntax very similar to the syntax used to make predictions from the tuned elastic net model.

Complete the code chunk below. You must make predictions on the visualization grid, viz_grid, using the trained neural network, nnet_default. Instruct the predict() function to return the probabilities by setting type = 'prob'.

SOLUTION

pred_viz_nnet_probs <- predict(nnet_default, newdata = viz_grid, type = "prob")

6c)

The code chunk below is completed for you. The pred_viz_nnet_probs dataframe is column binded to the viz_grid dataframe. The new object, viz_nnet_df, provides the class predicted probabilities for each input combination in the visualization grid. A glimpse is printed to the screen. Please not the eval flag is set to eval=FALSE in the code chunk below. You must change eval to eval=TRUE in the chunk options to make sure the code chunk below runs when you knit the markdown.

viz_nnet_df <- viz_grid %>% bind_cols(pred_viz_nnet_probs)

viz_nnet_df %>% glimpse()
## Rows: 1,377
## Columns: 5
## $ x1        <chr> "C", "B", "A", "C", "B", "A", "C", "B", "A", "C", "B", "A", …
## $ x2        <dbl> -3.00, -3.00, -3.00, -2.25, -2.25, -2.25, -1.50, -1.50, -1.5…
## $ x3        <dbl> -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, …
## $ event     <dbl> 0.8247354, 0.2403616, 0.3114790, 0.7902554, 0.3992162, 0.516…
## $ non_event <dbl> 0.17526457, 0.75963841, 0.68852102, 0.20974462, 0.60078381, …

The glimpse reveals that the event column stores the predicted event probability. You must visualize the predicted event probability in a manner consistent with the viz_bayes_logpost_preds() function and the tuned elastic net model predictions. You will visualize the predicted probability as a line (curve) with respect to x3, for each combination of x2 and x1.

Pipe the viz_nnet_df object to ggplot(). Map the x aesthetic to the x3 variable and the y aesthetic to the event variable. Add a geom_line() layer and map the color aesthetic to the x1 variable. Manually assign the size to 1.2. Create the facets by including the facet_wrap() function and specify the facets are “functions of” the x2 input.

SOLUTION

Add your code chunk here.

viz_nnet_df %>% ggplot(aes(x = x3, y = event, color = x1)) +
                geom_line(size = 1.2) +
                facet_wrap(~x2)

6d)

Let’s now a tree based method. You will use the default tuning grid and thus do not need to specify tuneGrid. Tree based models do not have the same kind of preprocessing requirements as other models. Thus, you do not need the preProcess argument in the caret::train() function call. We will discuss why that is the case in lecture.

Train a random forest binary classifier by setting the method argument equal to "rf". You must set importance = TRUE in the caret::train() function call. You should not define any interactions or derive features from the inputs in the formula interface. The formula interface should “look” like linear additive features. Assign the result to the rf_default variable. Display the rf_default object to the screen.

IMPORTANT: caret will prompt you in the R Console to install the randomForest package if you do not have it. Follow the instructions.

SOLUTION

The random seed is set for you for reproducibility. You may add more code chunks if you like.

PLEASE NOTE: This code chunk may take several minutes to complete!

set.seed(1234)

rf_default <- train(outcome ~ ., data = df_caret, method = "rf", metric = my_metric, importance = TRUE, trControl = my_ctrl)
rf_default
## Random Forest 
## 
## 225 samples
##   3 predictor
##   2 classes: 'event', 'non_event' 
## 
## No pre-processing
## Resampling: Cross-Validated (10 fold, repeated 3 times) 
## Summary of sample sizes: 202, 202, 202, 203, 202, 202, ... 
## Resampling results across tuning parameters:
## 
##   mtry  Accuracy   Kappa    
##   2     0.8074769  0.5274879
##   3     0.7925889  0.4992796
##   4     0.7927811  0.5032971
## 
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was mtry = 2.

6e)

Let’s examine the random forest behavior through predictions.

Complete the code chunk below. You must make predictions on the visualization grid, viz_grid, using the random forest model rf_default``. Instruct thepredict()function to return the probabilities by settingtype = ‘prob’`.

SOLUTION

pred_viz_rf_probs <- predict(rf_default, viz_grid, type = 'prob')

6f)

The code chunk below is completed for you. The pred_viz_rf_probs dataframe is column binded to the viz_grid dataframe. The new object, viz_rf_df, provides the class predicted probabilities for each input combination in the visualization grid according to the random forest model. A glimpse is printed to the screen. Please not the eval flag is set to eval=FALSE in the code chunk below. You must change eval to eval=TRUE in the chunk options to make sure the code chunk below runs when you knit the markdown.

viz_rf_df <- viz_grid %>% bind_cols(pred_viz_rf_probs)

viz_rf_df %>% glimpse()
## Rows: 1,377
## Columns: 5
## $ x1        <chr> "C", "B", "A", "C", "B", "A", "C", "B", "A", "C", "B", "A", …
## $ x2        <dbl> -3.00, -3.00, -3.00, -2.25, -2.25, -2.25, -1.50, -1.50, -1.5…
## $ x3        <dbl> -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, …
## $ event     <dbl> 0.490, 0.404, 0.440, 0.490, 0.404, 0.440, 0.492, 0.406, 0.44…
## $ non_event <dbl> 0.510, 0.596, 0.560, 0.510, 0.596, 0.560, 0.508, 0.594, 0.55…

The glimpse reveals that the event column stores the predicted event probability. You must visualize the predicted event probability in the same fashion as you did in 6c).

Pipe the viz_rf_df object to ggplot(). Map the x aesthetic to the x3 variable and the y aesthetic to the event variable. Add a geom_line() layer and map the color aesthetic to the x1 variable. Manually assign the size to 1.2. Create the facets by including the facet_wrap() function and specify the facets are “functions of” the x2 input.

SOLUTION

Add your code chunks here.

viz_rf_df %>%
  ggplot(mapping = aes(x = x3, y = event, color = x1)) +
  geom_line(size = 1.2) +
  facet_wrap(~x2,)

6g)

You should have included importance = TRUE in the caret::train() call in 6d). This allows the random forest specific variable importance rankings to be returned.

Create a plot to show the variable importance rankings associated with the random forest model. Are the importance rankings consistent with the rankings from the elastic net model?

SOLUTION

Add your code chunks here.

plot(varImp(rf_default))

Problem 07

Lastly, let’s compare the various caret trained models based on our resampling scheme.

7a)

Complete the first code chunk below which compiles the defaul elastic net, tuned elastic net, default neural network, and the default random forest models together.

The field names in the list state which model should be assigned.

SOLUTION

caret_acc_compare <- resamples(list(ENET_default = enet_default,
                                    ENET_tune = enet_tune,
                                    NNET_default = nnet_default,
                                    RF_default = rf_default))

7b)

Visually compare the models based on the resampled Accuracy with a dotplot.

Use the dotplot() function to visualize the resampled performance summary for each model. Assign the metric argument to 'Accuracy' to force the dotplot() function to only show Accuracy.

Which model is the best for this application?

SOLUTION

Add your code chunks here.

dotplot(caret_acc_compare, metric = "Accuracy")

7c)

How would you describe the differences in the predictions between the 3 types of models you trained in this application?

SOLUTION

What do you think?